package controllers

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"net/http"
)

type WebSocketController struct {
	BaseController
}

type Client struct {
	id   string
	conn *websocket.Conn // 用户websocket连接
	name string          // 用户名称
}

type Message struct {
	EventType   byte                   `json:"type"`
	Name        string                 `json:"name"`    // 用户名称
	Message     string                 `json:"message"` // 消息内容
	MessageFrom string                 `json:"message_from"`
	MessageTo   string                 `json:"message_to"`
	Data        map[string]interface{} `json:"data"`
}

const TypeSend = 0  // 用户发布消息
const TypeJoin = 1  // 用户进入
const TypeLeave = 2 // 用户退出
const TypeUser = 3  // 连接的用户返回他的信息

var clients = make(map[Client]bool) // 用户组映射

// 此处要设置有缓冲的通道。因为这是goroutine自己从通道中发送并接受数据。
// 若是无缓冲的通道，该goroutine发送数据到通道后就被锁定，需要数据被接受后才能解锁，而恰恰接受数据的又只能是它自己
var join = make(chan Client, 10)       // 用户加入通道
var leave = make(chan Client, 10)      // 用户退出通道
var send = make(chan Message, 10)      // 消息通道
var sendToAll = make(chan Message, 10) // 消息通道

var upgrader = websocket.Upgrader{
	// 跨域
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func init() {
	go broadcaster()
}

// Index 用于与用户间的websocket连接(chatRoom.html发送来的websocket请求)
func (c *WebSocketController) Index(r *gin.Context) {
	name := r.Query("name")
	if len(name) == 0 {
		http.Error(r.Writer, "用户名是必须的", 400)
		return
	}

	// 检验http头中upgrader属性，若为websocket，则将http协议升级为websocket协议
	conn, err := upgrader.Upgrade(r.Writer, r.Request, nil)

	if _, ok := err.(websocket.HandshakeError); ok {
		fmt.Println("不是 websocket 连接")
		http.Error(r.Writer, "不是 websocket 握手", 400)
		return
	} else if err != nil {
		fmt.Println("无法设置 WebSocket 连接：", err)
		return
	}

	client := Client{
		id:   uuid.NewString(),
		name: name,
		conn: conn,
	}

	// 如果用户列表中没有该用户
	if !clients[client] {
		join <- client
		fmt.Println("user:", client.name, "websocket连接成功！")
	}

	// 当函数返回时，将该用户加入退出通道，并断开用户连接
	defer func() {
		leave <- client
		_ = client.conn.Close()
	}()

	onMessage(client)
}

func onMessage(client Client) {
	// 由于WebSocket一旦连接，便可以保持长时间通讯，则该接口函数可以一直运行下去，直到连接断开
	for {
		// 读取消息。如果连接断开，则会返回错误
		_, msgStr, err := client.conn.ReadMessage()

		// 如果返回错误，就退出循环
		if err != nil {
			break
		}

		fmt.Println("websocket收到消息:" + string(msgStr))

		var msg Message
		msg.Name = client.name
		msg.EventType = TypeSend

		jsonErr := json.Unmarshal(msgStr, &msg)

		if jsonErr != nil {
			msg.Message = "消息内容解析失败..."
		}

		if msg.MessageTo != "" {
			send <- msg
		} else {
			sendToAll <- msg
		}
	}
}

func sendMessageToAll(msg Message) {
	str := fmt.Sprintf("广播-----------%s 发送信息: %s\n", msg.Name, msg.Message)
	fmt.Println(str)
	// 将某个用户发出的消息发送给所有用户
	for client := range clients {
		// 将数据编码成json形式，data是[]byte类型
		// json.Marshal()只会编码结构体中公开的属性(即大写字母开头的属性)
		data, err := json.Marshal(msg)
		if err != nil {
			fmt.Println("无法解析消息：", err)
			return
		}
		// fmt.Println("=======the json message is", string(data))  // 转换成字符串类型便于查看
		if client.conn.WriteMessage(websocket.TextMessage, data) != nil {
			fmt.Println("写消息失败")
		}
	}
}

func sendMessage(msg Message) {
	var toClient Client

	for client := range clients {
		if client.id == msg.MessageTo {
			toClient = client
		}
	}

	data, err := json.Marshal(msg)
	if err != nil {
		fmt.Println("无法解析消息：", err)
		return
	}
	fmt.Println("=======the json message is", string(data)) // 转换成字符串类型便于查看
	if toClient.conn.WriteMessage(websocket.TextMessage, data) != nil {
		fmt.Println("写消息失败")
	}
}

func getUserList() map[string]interface{} {
	// 所有在线用户列表
	var userList []map[string]string

	for item := range clients {
		userList = append(userList, map[string]string{
			"id":   item.id,
			"name": item.name,
		})
	}

	return map[string]interface{}{
		"user_list": userList,
	}
}

func joinHandler(client Client) {
	str := fmt.Sprintf("广播-----------%s 加入聊天室\n", client.name)
	fmt.Println(str)

	clients[client] = true // 将用户加入映射

	// 将用户加入消息放入消息通道
	var msg Message
	msg.Name = client.name
	msg.EventType = TypeJoin
	msg.Message = fmt.Sprintf("%s 加入了聊天...", client.name)
	msg.Data = getUserList()

	// 此处要设置有缓冲的通道。因为这是goroutine自己从通道中发送并接受数据。
	// 若是无缓冲的通道，该goroutine发送数据到通道后就被锁定，需要数据被接受后才能解锁，而恰恰接受数据的又只能是它自己
	sendToAll <- msg

	// 给用户返回他自己的信息
	msg.EventType = TypeUser
	msg.MessageTo = client.id

	send <- msg
}

func leaveHandler(client Client) {
	str := fmt.Sprintf("广播-----------%s 离开聊天室\n", client.name)
	fmt.Println(str)

	// 如果该用户已经被删除
	if !clients[client] {
		fmt.Println("用户已离开，用户姓名：" + client.name)
		return
	}

	delete(clients, client) // 将用户从映射中删除

	// 将用户退出消息放入消息通道
	var msg Message
	msg.Name = client.name
	msg.EventType = TypeLeave
	msg.Message = fmt.Sprintf("%s 已退出...", client.name)
	msg.Data = getUserList()

	sendToAll <- msg
}

// 广播
func broadcaster() {
	for {
		// 哪个case可以执行，则转入到该case。若都不可执行，则堵塞。
		select {
		// 消息通道中有消息则执行，否则堵塞
		case msg := <-sendToAll:
			sendMessageToAll(msg)
		case msg := <-send:
			sendMessage(msg)
		// 有用户加入
		case client := <-join:
			joinHandler(client)
		// 有用户退出
		case client := <-leave:
			leaveHandler(client)
		}
	}
}
